{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Полезные практики, неочевидные моменты"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Неоднозная грамматика"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть примитивная рекурсивная грамматика:\n",
    "```\n",
    "A -> a | a a\n",
    "B -> A B | A\n",
    "```\n",
    "\n",
    "Есть строка \"a a a\". Парсер может разобрать её 3 способами:\n",
    "```\n",
    "(a) (a) (a)\n",
    "(a) (a a)\n",
    "(a a) (a)\n",
    "```\n",
    "\n",
    "Yargy парсер перебирает все варианты разбора. Используем непубличный метод `extract`, посмотрим все варианты:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "R0 -> R1 R0 | R1\n",
       "R1 -> 'a' | 'a' 'a'\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"75pt\" height=\"108pt\"\n",
       " viewBox=\"0.00 0.00 74.50 108.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 104)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-104 70.5,-104 70.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"44,-100 15,-100 15,-81 44,-81 44,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"29.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"29,-73 0,-73 0,-54 29,-54 29,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M24.25,-80.76C23.32,-79.21 22.34,-77.57 21.38,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"22.11,-75.14 19.67,-73.11 20.31,-76.23 22.11,-75.14\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"59,-73 30,-73 30,-54 59,-54 59,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M34.75,-80.76C35.68,-79.21 36.66,-77.57 37.62,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.69,-76.23 39.33,-73.11 36.89,-75.14 38.69,-76.23\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"25.5,-46 3.5,-46 3.5,-27 25.5,-27 25.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M14.5,-53.76C14.5,-52.36 14.5,-50.9 14.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"15.55,-49.11 14.5,-46.11 13.45,-49.11 15.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"59,-46 30,-46 30,-27 59,-27 59,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M44.5,-53.76C44.5,-52.36 44.5,-50.9 44.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"45.55,-49.11 44.5,-46.11 43.45,-49.11 45.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"43.5,-19 21.5,-19 21.5,0 43.5,0 43.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"32.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M40.3,-26.76C39.56,-25.21 38.78,-23.57 38,-21.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.88,-21.36 36.63,-19.11 36.99,-22.27 38.88,-21.36\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"66.5,-19 44.5,-19 44.5,0 66.5,0 66.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"55.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M48.35,-26.76C49.03,-25.21 49.75,-23.57 50.46,-21.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"51.46,-22.28 51.71,-19.11 49.54,-21.44 51.46,-22.28\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"85pt\" height=\"108pt\"\n",
       " viewBox=\"0.00 0.00 84.50 108.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 104)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-104 80.5,-104 80.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"61.5,-100 32.5,-100 32.5,-81 61.5,-81 61.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"46.5,-73 17.5,-73 17.5,-54 46.5,-54 46.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"32\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M41.75,-80.76C40.82,-79.21 39.84,-77.57 38.88,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.61,-75.14 37.17,-73.11 37.81,-76.23 39.61,-75.14\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76.5,-73 47.5,-73 47.5,-54 76.5,-54 76.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;4 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M52.25,-80.76C53.18,-79.21 54.16,-77.57 55.12,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"56.19,-76.23 56.83,-73.11 54.39,-75.14 56.19,-76.23\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"22,-46 0,-46 0,-27 22,-27 22,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"11\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M24.66,-53.76C23.22,-52.05 21.71,-50.25 20.22,-48.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"20.97,-47.73 18.23,-46.11 19.36,-49.08 20.97,-47.73\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"45,-46 23,-46 23,-27 45,-27 45,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"34\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M32.7,-53.76C32.81,-52.36 32.93,-50.9 33.04,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"34.12,-49.19 33.31,-46.11 32.03,-49.02 34.12,-49.19\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76.5,-46 47.5,-46 47.5,-27 76.5,-27 76.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62,-53.76C62,-52.36 62,-50.9 62,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.05,-49.11 62,-46.11 60.95,-49.11 63.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"73,-19 51,-19 51,0 73,0 73,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62,-26.76C62,-25.36 62,-23.9 62,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.05,-22.11 62,-19.11 60.95,-22.11 63.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"95pt\" height=\"135pt\"\n",
       " viewBox=\"0.00 0.00 95.00 135.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 131)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-131 91,-131 91,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"44,-127 15,-127 15,-108 44,-108 44,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"29.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"29,-100 0,-100 0,-81 29,-81 29,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M24.25,-107.76C23.32,-106.21 22.34,-104.57 21.38,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"22.11,-102.14 19.67,-100.11 20.31,-103.23 22.11,-102.14\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"59,-100 30,-100 30,-81 59,-81 59,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M34.75,-107.76C35.68,-106.21 36.66,-104.57 37.62,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.69,-103.23 39.33,-100.11 36.89,-102.14 38.69,-103.23\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"25.5,-73 3.5,-73 3.5,-54 25.5,-54 25.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M14.5,-80.76C14.5,-79.36 14.5,-77.9 14.5,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"15.55,-76.11 14.5,-73.11 13.45,-76.11 15.55,-76.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"57,-73 28,-73 28,-54 57,-54 57,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"42.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M43.8,-80.76C43.69,-79.36 43.57,-77.9 43.46,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"44.47,-76.02 43.19,-73.11 42.38,-76.19 44.47,-76.02\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"87,-73 58,-73 58,-54 87,-54 87,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;6 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M54.29,-80.76C56.29,-78.98 58.41,-77.08 60.47,-75.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"61.32,-75.89 62.85,-73.11 59.92,-74.33 61.32,-75.89\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"53.5,-46 31.5,-46 31.5,-27 53.5,-27 53.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"42.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M42.5,-53.76C42.5,-52.36 42.5,-50.9 42.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"43.55,-49.11 42.5,-46.11 41.45,-49.11 43.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"87,-46 58,-46 58,-27 87,-27 87,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 6&#45;&gt;7 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>6&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M72.5,-53.76C72.5,-52.36 72.5,-50.9 72.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"73.55,-49.11 72.5,-46.11 71.45,-49.11 73.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"83.5,-19 61.5,-19 61.5,0 83.5,0 83.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>7&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M72.5,-26.76C72.5,-25.36 72.5,-23.9 72.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"73.55,-22.11 72.5,-19.11 71.45,-22.11 73.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from yargy.parser import prepare_trees\n",
    "from yargy import Parser, or_, rule\n",
    "\n",
    "A = or_(\n",
    "    rule('a'),\n",
    "    rule('a', 'a')    \n",
    ")\n",
    "B = A.repeatable()\n",
    "display(B.normalized.as_bnf)\n",
    "\n",
    "parser = Parser(B)\n",
    "matches = parser.extract('a a a')\n",
    "for match in matches:\n",
    "    # кроме 3-х полных разборов, парсёр найдёт ещё 7 частичных: (a) _ _, (a a) _, (a) (a) _, ...\n",
    "    # не будем их показывать\n",
    "    if len(match.tokens) == 3:\n",
    "        display(match.tree.as_dot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Число вариантов быстро растёт. Для строки \"a x 10\", парсер переберёт 89 разборов. Для \"a x 20\" — 979 и потратит заметное количество времени.\n",
    "\n",
    "При работе с естественным русским языком, мы построянно сталкиваемся с неоднозначными грамматиками. Например, список из трёх взысканий по арбиражному делу: \"5 тыс. р. штраф пени 3 тыс. р. необоснованного обогащения\". Эскиз грамматики:\n",
    "```\n",
    "MONEY -> INT тыс. р.\n",
    "TYPE -> штраф | пени | необоснованное обогащение\n",
    "\n",
    "# 1. \"5 тыс. р. штраф\"\n",
    "# 2. \"штраф 5 тыс. р.\"\n",
    "# 3. \"3 тыс. р.\" — только сумма\n",
    "# 4. \"пени\" — только тип\n",
    "PENALTY -> MONEY TYPE | TYPE MONEY | MONEY | TYPE\n",
    "\n",
    "PENALTIES -> PENALTY+\n",
    "```\n",
    "\n",
    "Получаем много вариантов разбора:\n",
    "```\n",
    "(5 тыс. р. штраф) (пени) (3 тыс. р. необоснованного обогащения)\n",
    "(5 тыс. р. штраф) (пени) (3 тыс. р.) (необоснованного обогащения)\n",
    "(5 тыс. р.) (штраф) (пени) (3 тыс. р.) (необоснованного обогащения)\n",
    "(5 тыс. р. штраф) (пени 3 тыс. р.) (необоснованного обогащения)\n",
    "...\n",
    "```\n",
    "\n",
    "Самый просто способ избежать комбинаторного взрыва числа разборов — ограничить `repeatable`. Вместо `PENALTIES = PENALTY.repeatable()`, напишем `PENALTIES = PENALTY.repeatable(max=5)`. Такое правило отбросить 6-е и последующие взыскания, но завершится в ограниченное время."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CappedParser"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть ещё один способ избежать комбинаторного взрыва числа разборов: выключать парсер, когда число состояний превысило порог. `CappedParser` наследует `Parser`, оборачивает внутренние методы `chart`, `predict`, `scan`, `complete` — шаги алгоритма Earley-парсера:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a a a \n",
      "OK\n",
      "a a a a \n",
      "OK\n",
      "a a a a a \n",
      "OK\n",
      "a a a a a a \n",
      "TooManyStates\n",
      "a a a a a a a \n",
      "TooManyStates\n",
      "a a a a a a a a \n",
      "TooManyStates\n",
      "a a a a a a a a a \n",
      "TooManyStates\n"
     ]
    }
   ],
   "source": [
    "class TooManyStates(Exception): pass\n",
    "\n",
    "\n",
    "def capped(method):\n",
    "    def wrap(self, column, *args):\n",
    "        before = len(column.states)\n",
    "        method(self, column, *args)\n",
    "        after = len(column.states)\n",
    "\n",
    "        self.states += (after - before)\n",
    "        if self.cap and self.states > self.cap:\n",
    "            raise TooManyStates\n",
    "\n",
    "    return wrap\n",
    "\n",
    "\n",
    "class CappedParser(Parser):\n",
    "    def reset(self):\n",
    "        self.states = 0\n",
    "\n",
    "    def __init__(self, *args, cap=None, **kwargs):\n",
    "        self.cap = cap\n",
    "        self.reset()\n",
    "        Parser.__init__(self, *args, **kwargs)\n",
    "\n",
    "    def chart(self, *args, **kwargs):\n",
    "        self.reset()\n",
    "        return Parser.chart(self, *args, **kwargs)\n",
    "\n",
    "    predict = capped(Parser.predict)\n",
    "    scan = capped(Parser.scan)\n",
    "    complete = capped(Parser.complete)\n",
    "    \n",
    "\n",
    "parser = CappedParser(B, cap=100)\n",
    "for size in range(3, 10):\n",
    "    text = 'a ' * size\n",
    "    print(text)\n",
    "    try:\n",
    "        parser.match(text)\n",
    "    except TooManyStates:\n",
    "        print('TooManyStates')\n",
    "    else:\n",
    "        print('OK')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Порядок аргументов в `or_` имеет значение"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда разборов больше одного, парсер возвращает самый левый вариант:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"95pt\" height=\"135pt\"\n",
       " viewBox=\"0.00 0.00 95.00 135.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 131)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-131 91,-131 91,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"44,-127 15,-127 15,-108 44,-108 44,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"29.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"29,-100 0,-100 0,-81 29,-81 29,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M24.25,-107.76C23.32,-106.21 22.34,-104.57 21.38,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"22.11,-102.14 19.67,-100.11 20.31,-103.23 22.11,-102.14\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"59,-100 30,-100 30,-81 59,-81 59,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M34.75,-107.76C35.68,-106.21 36.66,-104.57 37.62,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.69,-103.23 39.33,-100.11 36.89,-102.14 38.69,-103.23\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"25.5,-73 3.5,-73 3.5,-54 25.5,-54 25.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"14.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M14.5,-80.76C14.5,-79.36 14.5,-77.9 14.5,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"15.55,-76.11 14.5,-73.11 13.45,-76.11 15.55,-76.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"57,-73 28,-73 28,-54 57,-54 57,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"42.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M43.8,-80.76C43.69,-79.36 43.57,-77.9 43.46,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"44.47,-76.02 43.19,-73.11 42.38,-76.19 44.47,-76.02\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"87,-73 58,-73 58,-54 87,-54 87,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;6 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M54.29,-80.76C56.29,-78.98 58.41,-77.08 60.47,-75.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"61.32,-75.89 62.85,-73.11 59.92,-74.33 61.32,-75.89\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"53.5,-46 31.5,-46 31.5,-27 53.5,-27 53.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"42.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M42.5,-53.76C42.5,-52.36 42.5,-50.9 42.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"43.55,-49.11 42.5,-46.11 41.45,-49.11 43.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"87,-46 58,-46 58,-27 87,-27 87,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 6&#45;&gt;7 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>6&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M72.5,-53.76C72.5,-52.36 72.5,-50.9 72.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"73.55,-49.11 72.5,-46.11 71.45,-49.11 73.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"83.5,-19 61.5,-19 61.5,0 83.5,0 83.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"72.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>7&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M72.5,-26.76C72.5,-25.36 72.5,-23.9 72.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"73.55,-22.11 72.5,-19.11 71.45,-22.11 73.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = or_(\n",
    "    rule('a'),\n",
    "    rule('a', 'a')    \n",
    ")\n",
    "B = A.repeatable()\n",
    "\n",
    "parser = Parser(B)\n",
    "match = parser.match('a a a')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Переставим местами `a a` и `a`, результат поменяется:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"85pt\" height=\"108pt\"\n",
       " viewBox=\"0.00 0.00 84.50 108.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 104)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-104 80.5,-104 80.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"61.5,-100 32.5,-100 32.5,-81 61.5,-81 61.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"46.5,-73 17.5,-73 17.5,-54 46.5,-54 46.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"32\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M41.75,-80.76C40.82,-79.21 39.84,-77.57 38.88,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.61,-75.14 37.17,-73.11 37.81,-76.23 39.61,-75.14\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76.5,-73 47.5,-73 47.5,-54 76.5,-54 76.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;4 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M52.25,-80.76C53.18,-79.21 54.16,-77.57 55.12,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"56.19,-76.23 56.83,-73.11 54.39,-75.14 56.19,-76.23\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"22,-46 0,-46 0,-27 22,-27 22,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"11\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M24.66,-53.76C23.22,-52.05 21.71,-50.25 20.22,-48.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"20.97,-47.73 18.23,-46.11 19.36,-49.08 20.97,-47.73\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"45,-46 23,-46 23,-27 45,-27 45,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"34\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M32.7,-53.76C32.81,-52.36 32.93,-50.9 33.04,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"34.12,-49.19 33.31,-46.11 32.03,-49.02 34.12,-49.19\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76.5,-46 47.5,-46 47.5,-27 76.5,-27 76.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62,-53.76C62,-52.36 62,-50.9 62,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.05,-49.11 62,-46.11 60.95,-49.11 63.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"73,-19 51,-19 51,0 73,0 73,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"62\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">a</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62,-26.76C62,-25.36 62,-23.9 62,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.05,-22.11 62,-19.11 60.95,-22.11 63.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = or_(\n",
    "    rule('a', 'a'),\n",
    "    rule('a')\n",
    ")\n",
    "B = A.repeatable()\n",
    "\n",
    "parser = Parser(B)\n",
    "match = parser.match('a a a')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На практике это важно. В примере со взыскиниями грамматика:\n",
    "```\n",
    "PENALTY -> MONEY TYPE | TYPE MONEY | MONEY | TYPE\n",
    "```\n",
    "\n",
    "Левый разбор, не то, что ожидалось:\n",
    "```\n",
    "(5 тыс. р. штраф) (пени 3 тыс. р.) (необоснованного обогащения)`\n",
    "```\n",
    "\n",
    "Переставим аргументы:\n",
    "```\n",
    "PENALTY -> MONEY TYPE |  TYPE | TYPE MONEY | MONEY\n",
    "```\n",
    "\n",
    "Получим:\n",
    "```\n",
    "(5 тыс. р. штраф) (пени) (3 тыс. р. необоснованного обогащения)`\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## IdTokenizer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Parser` принимает на вход текст. Первым делом парсер разделяет текст на токены. Токенизатор передаётся необязательным аргументом `tokenizer`: `Parser(RULE, tokenizer=Tokenizer())`. Токенизатор по-умолчанию — `yargy.tokenizer.MorphTokenizer`.\n",
    "\n",
    "Бывает нужно обработать уже токенизированный текст. Например, есть два парсера, нужно обоими обработать один текст. Хотим сэкономить время, не токенизировать текст дважды. Заведём парсер-обёртку, он ничего не делает, принимает и возращает токены:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy.tokenizer import (\n",
    "    Tokenizer,\n",
    "    MorphTokenizer,\n",
    "    EOL\n",
    ")\n",
    "\n",
    "\n",
    "# Стандартный токенизатор. Удаляем правило для переводом строк.\n",
    "# Обычно токены с '\\n' только мешаются.\n",
    "TOKENIZER = MorphTokenizer().remove_types(EOL)\n",
    "\n",
    "\n",
    "class IdTokenizer(Tokenizer):\n",
    "    def __init__(self, tokenizer):\n",
    "        self.tokenizer = tokenizer\n",
    "\n",
    "    # Используется при инициализации morph_pipeline, caseless_pipeline.\n",
    "    # Строки-аргументы pipeline нужно разделить на слова. Как разделить,\n",
    "    # например, \"кейс| |dvd-диска\" или \"кейс| |dvd|-|диска\"? Используем стандартный токенизатор.\n",
    "    def split(self, text):\n",
    "        return self.tokenizer.split(text)\n",
    "\n",
    "    # Используется при инициализации предикатов. Например, есть предикат type('INT').\n",
    "    # Поддерживает ли токенизатор тип INT?\n",
    "    def check_type(self, type):\n",
    "        return self.tokenizer.check_type(type)\n",
    "\n",
    "    @property\n",
    "    def morph(self):\n",
    "        return self.tokenizer.morph\n",
    "\n",
    "    def __call__(self, tokens):\n",
    "        return tokens\n",
    "\n",
    "\n",
    "ID_TOKENIZER = IdTokenizer(TOKENIZER)\n",
    "\n",
    "tokens = TOKENIZER('a a a a')\n",
    "parser = Parser(B, tokenizer=ID_TOKENIZER)\n",
    "parser.match(tokens);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `ValueError: no .interpretation(...) for root rule`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть два правила, хотим найти факты, где сработало одно из них:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy.interpretation import fact\n",
    "\n",
    "\n",
    "F = fact('F', ['a'])\n",
    "G = fact('G', ['b'])\n",
    "\n",
    "\n",
    "A = rule('a').interpretation(F.a).interpretation(F)\n",
    "B = rule('b').interpretation(G.b).interpretation(G)\n",
    "C = or_(A, B)\n",
    "parser = Parser(C)\n",
    "\n",
    "match = parser.match('a')\n",
    "# match.fact ValueError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ожидаем `F(a='a')`, получаем `ValueError: no .interpretation(...) for root rule`. На вершине-корне нет пометки контруктора факта:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"135pt\" height=\"162pt\"\n",
       " viewBox=\"0.00 0.00 135.00 162.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 158)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-158 131,-158 131,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76,-154 49,-154 49,-135 76,-135 76,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"62.5\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">Or</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-127 39,-127 39,-108 62,-108 62,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">F</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M58.3,-134.76C57.56,-133.21 56.78,-131.57 56,-129.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"56.88,-129.36 54.63,-127.11 54.99,-130.27 56.88,-129.36\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"89.5,-127 65.5,-127 65.5,-108 89.5,-108 89.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"77.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">G</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M67.75,-134.76C68.68,-133.21 69.66,-131.57 70.62,-129.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"71.69,-130.23 72.33,-127.11 69.89,-129.14 71.69,-130.23\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-100 33,-100 33,-81 62,-81 62,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"47.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">F.a</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M49.45,-107.76C49.28,-106.36 49.11,-104.9 48.93,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"49.93,-102.97 48.53,-100.11 47.85,-103.22 49.93,-102.97\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"96.5,-100 64.5,-100 64.5,-81 96.5,-81 96.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"80.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">G.b</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>2&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M78.55,-107.76C78.72,-106.36 78.89,-104.9 79.07,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"80.15,-103.22 79.47,-100.11 78.07,-102.97 80.15,-103.22\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"63,-73 26,-73 26,-54 63,-54 63,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Rule</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M46.45,-80.76C46.28,-79.36 46.11,-77.9 45.93,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"46.93,-75.97 45.53,-73.11 44.85,-76.22 46.93,-75.97\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"107,-73 70,-73 70,-54 107,-54 107,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"88.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Rule</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M83.3,-80.76C83.74,-79.36 84.21,-77.9 84.68,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"85.83,-76.29 85.74,-73.11 83.83,-75.65 85.83,-76.29\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"63,-46 0,-46 0,-27 63,-27 63,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"31.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Production</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;7 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>5&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M39.95,-53.76C39.15,-52.21 38.3,-50.57 37.46,-48.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.29,-48.29 35.98,-46.11 36.43,-49.26 38.29,-48.29\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"127,-46 64,-46 64,-27 127,-27 127,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"95.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Production</text>\n",
       "</g>\n",
       "<!-- 6&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>6&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M90.95,-53.76C91.34,-52.36 91.75,-50.9 92.15,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"93.29,-49.28 93.09,-46.11 91.27,-48.72 93.29,-49.28\"/>\n",
       "</g>\n",
       "<!-- 9 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>9</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"44.5,-19 18.5,-19 18.5,0 44.5,0 44.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"31.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">&#39;a&#39;</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;9 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>7&#45;&gt;9</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M31.5,-26.76C31.5,-25.36 31.5,-23.9 31.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"32.55,-22.11 31.5,-19.11 30.45,-22.11 32.55,-22.11\"/>\n",
       "</g>\n",
       "<!-- 10 -->\n",
       "<g id=\"node11\" class=\"node\">\n",
       "<title>10</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"108.5,-19 82.5,-19 82.5,0 108.5,0 108.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"95.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">&#39;b&#39;</text>\n",
       "</g>\n",
       "<!-- 8&#45;&gt;10 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>8&#45;&gt;10</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M95.5,-26.76C95.5,-25.36 95.5,-23.9 95.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"96.55,-22.11 95.5,-19.11 94.45,-22.11 96.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "C.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим прокси-факт:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"135pt\" height=\"216pt\"\n",
       " viewBox=\"0.00 0.00 135.00 216.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 212)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-212 131,-212 131,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"83.5,-208 41.5,-208 41.5,-189 83.5,-189 83.5,-208\"/>\n",
       "<text text-anchor=\"middle\" x=\"62.5\" y=\"-196\" font-family=\"sans\" font-size=\"10.00\">Proxy</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"96,-181 29,-181 29,-162 96,-162 96,-181\"/>\n",
       "<text text-anchor=\"middle\" x=\"62.5\" y=\"-169\" font-family=\"sans\" font-size=\"10.00\">Proxy.value</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62.5,-188.76C62.5,-187.36 62.5,-185.9 62.5,-184.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.55,-184.11 62.5,-181.11 61.45,-184.11 63.55,-184.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"76,-154 49,-154 49,-135 76,-135 76,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"62.5\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">Or</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M62.5,-161.76C62.5,-160.36 62.5,-158.9 62.5,-157.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"63.55,-157.11 62.5,-154.11 61.45,-157.11 63.55,-157.11\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-127 39,-127 39,-108 62,-108 62,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"50.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">F</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;3 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>2&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M58.3,-134.76C57.56,-133.21 56.78,-131.57 56,-129.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"56.88,-129.36 54.63,-127.11 54.99,-130.27 56.88,-129.36\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"89.5,-127 65.5,-127 65.5,-108 89.5,-108 89.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"77.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">G</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>2&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M67.75,-134.76C68.68,-133.21 69.66,-131.57 70.62,-129.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"71.69,-130.23 72.33,-127.11 69.89,-129.14 71.69,-130.23\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-100 33,-100 33,-81 62,-81 62,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"47.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">F.a</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M49.45,-107.76C49.28,-106.36 49.11,-104.9 48.93,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"49.93,-102.97 48.53,-100.11 47.85,-103.22 49.93,-102.97\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"96.5,-100 64.5,-100 64.5,-81 96.5,-81 96.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"80.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">G.b</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M78.55,-107.76C78.72,-106.36 78.89,-104.9 79.07,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"80.15,-103.22 79.47,-100.11 78.07,-102.97 80.15,-103.22\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"63,-73 26,-73 26,-54 63,-54 63,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"44.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Rule</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;7 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>5&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M46.45,-80.76C46.28,-79.36 46.11,-77.9 45.93,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"46.93,-75.97 45.53,-73.11 44.85,-76.22 46.93,-75.97\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"107,-73 70,-73 70,-54 107,-54 107,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"88.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Rule</text>\n",
       "</g>\n",
       "<!-- 6&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>6&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M83.3,-80.76C83.74,-79.36 84.21,-77.9 84.68,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"85.83,-76.29 85.74,-73.11 83.83,-75.65 85.83,-76.29\"/>\n",
       "</g>\n",
       "<!-- 9 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>9</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"63,-46 0,-46 0,-27 63,-27 63,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"31.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Production</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;9 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>7&#45;&gt;9</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M39.95,-53.76C39.15,-52.21 38.3,-50.57 37.46,-48.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"38.29,-48.29 35.98,-46.11 36.43,-49.26 38.29,-48.29\"/>\n",
       "</g>\n",
       "<!-- 10 -->\n",
       "<g id=\"node11\" class=\"node\">\n",
       "<title>10</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"127,-46 64,-46 64,-27 127,-27 127,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"95.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Production</text>\n",
       "</g>\n",
       "<!-- 8&#45;&gt;10 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>8&#45;&gt;10</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M90.95,-53.76C91.34,-52.36 91.75,-50.9 92.15,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"93.29,-49.28 93.09,-46.11 91.27,-48.72 93.29,-49.28\"/>\n",
       "</g>\n",
       "<!-- 11 -->\n",
       "<g id=\"node12\" class=\"node\">\n",
       "<title>11</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"44.5,-19 18.5,-19 18.5,0 44.5,0 44.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"31.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">&#39;a&#39;</text>\n",
       "</g>\n",
       "<!-- 9&#45;&gt;11 -->\n",
       "<g id=\"edge11\" class=\"edge\">\n",
       "<title>9&#45;&gt;11</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M31.5,-26.76C31.5,-25.36 31.5,-23.9 31.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"32.55,-22.11 31.5,-19.11 30.45,-22.11 32.55,-22.11\"/>\n",
       "</g>\n",
       "<!-- 12 -->\n",
       "<g id=\"node13\" class=\"node\">\n",
       "<title>12</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"108.5,-19 82.5,-19 82.5,0 108.5,0 108.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"95.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">&#39;b&#39;</text>\n",
       "</g>\n",
       "<!-- 10&#45;&gt;12 -->\n",
       "<g id=\"edge12\" class=\"edge\">\n",
       "<title>10&#45;&gt;12</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M95.5,-26.76C95.5,-25.36 95.5,-23.9 95.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"96.55,-22.11 95.5,-19.11 94.45,-22.11 96.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "F(\n",
       "    a='a'\n",
       ")"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Proxy = fact('Proxy', ['value'])\n",
    "\n",
    "C = or_(A, B).interpretation(Proxy.value).interpretation(Proxy)\n",
    "display(C.as_dot)\n",
    "\n",
    "parser = Parser(C)\n",
    "match = parser.match('a')\n",
    "match.fact.value\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `TypeError: mixed types`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Набор аргументов `or_` бывает двух видов:\n",
    "1. Все предикаты, тогда результат — предикат\n",
    "2. Все `rule`, тогда результат — `rule`\n",
    "\n",
    "Иногда правило состоит из одного предиката, передаём его в `or_`, получаем ошибку:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy.predicates import caseless\n",
    "\n",
    "A = rule('a')\n",
    "B = caseless('b')\n",
    "# or_(A, B)  # TypeError: mixed types: [<class 'yargy.rule.constructors.Rule'>, <class 'yargy.predicates.bank.eq'>]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Явно завернём предикат в `rule`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = rule(caseless('b'))\n",
    "C = or_(A, B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Машинное обучение и Yargy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть текст размеченный BIO-тегами:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = '15 апреля в Симферополе Леонид Рожков ...'\n",
    "tags = 'B I O B B I O'.split()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Parser` принимает необязательный аргумент `tagger`: `Parser(RULE, tagger=Tagger)`. `Tagger` принимает и возвращает список токенов. Добавим внешнюю разметку `tags` в токены. Используем предикат `tag`, извлечём сущности:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['15', 'апреля']\n",
      "['Симферополе']\n",
      "['Леонид', 'Рожков']\n"
     ]
    }
   ],
   "source": [
    "from yargy.tagger import Tagger\n",
    "from yargy.predicates import tag\n",
    "\n",
    "\n",
    "class Tagger(Tagger):\n",
    "    # Все возможные теги. Используется при инициализации предиката tag.\n",
    "    # Если пользователь создаст tag('FOO'), будет ошибка\n",
    "    tags = {'B', 'I', 'O'}\n",
    "\n",
    "    def __call__(self, tokens):\n",
    "        for token, tag in zip(tokens, tags):\n",
    "            yield token.tagged(tag)\n",
    "\n",
    "\n",
    "RULE = rule(\n",
    "    tag('B'),\n",
    "    tag('I').repeatable().optional()\n",
    ")\n",
    "parser = Parser(RULE, tagger=Tagger())\n",
    "\n",
    "matches = parser.findall(text)\n",
    "for match in matches:\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Пропустить часть текста"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть текст \"взыскать 5 тыс. р. штрафа, а также пени и неустойку\". Нужно выделить 3 взыскания \"5 тыс. р. штраф\", \"пени\", \"неустойка\", пропустить подстроки \", а также\", \"и\". Запустить парсер 2 раза: сначала выделим взыскания, удалим лишние токены, запустим парсер ещё раз: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[9, 25), [35, 39), [42, 51)]\n",
      "['5', 'тыс', '.', 'р', '.', 'штрафа', 'пени', 'неустойку']\n"
     ]
    }
   ],
   "source": [
    "from yargy.pipelines import morph_pipeline\n",
    "\n",
    "text = 'взыскать 5 тыс. р. штрафа, а также пени и неустойку'\n",
    "tokens = list(TOKENIZER(text))\n",
    "\n",
    "\n",
    "PAYMENT = morph_pipeline([\n",
    "    '5 тыс. р. штраф',\n",
    "    'пени',\n",
    "    'неустойка'\n",
    "])\n",
    "parser = Parser(PAYMENT, tokenizer=ID_TOKENIZER)\n",
    "\n",
    "matches = parser.findall(tokens)\n",
    "spans = [_.span for _ in matches]\n",
    "print(spans)\n",
    "\n",
    "\n",
    "def is_inside_span(token, span):\n",
    "    token_span = token.span\n",
    "    return span.start <= token_span.start and token_span.stop <= span.stop\n",
    "\n",
    "\n",
    "def select_span_tokens(tokens, spans):\n",
    "    for token in tokens:\n",
    "        if any(is_inside_span(token, _) for _ in spans):\n",
    "            yield token\n",
    "\n",
    "\n",
    "tokens = list(select_span_tokens(tokens, spans))\n",
    "print([_.value for _ in tokens])\n",
    "\n",
    "PAYMENTS = PAYMENT.repeatable()\n",
    "parser = Parser(PAYMENTS, tokenizer=ID_TOKENIZER)\n",
    "match = parser.match(tokens)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Генерация правил"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В Yargy все правила описываются на языке Python. Создадим функцию, которая генерирует правило. Например, правило для текста в скобочка и кавычках:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['[', 'a', 'b', ']']\n",
      "['«', 'e', 'f', '»']\n"
     ]
    }
   ],
   "source": [
    "from yargy import not_\n",
    "from yargy.predicates import eq\n",
    "\n",
    "\n",
    "def bounded(start, stop):\n",
    "    return rule(\n",
    "        eq(start),\n",
    "        not_(eq(stop)).repeatable(),\n",
    "        eq(stop)\n",
    "    )\n",
    "\n",
    "\n",
    "BOUNDED = or_(\n",
    "    bounded('[', ']'),\n",
    "    bounded('«', '»')\n",
    ")\n",
    "parser = Parser(BOUNDED)\n",
    "matches = parser.findall('[a b] {c d} «e f»')\n",
    "for match in matches:\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Правило — аналог `join` в Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy.predicates import in_\n",
    "\n",
    "\n",
    "def joined(ITEM, SEP):\n",
    "    return rule(\n",
    "        ITEM,\n",
    "        rule(\n",
    "            SEP,\n",
    "            ITEM\n",
    "        ).repeatable().optional()\n",
    "    )\n",
    "\n",
    "\n",
    "SEP = in_(',;')\n",
    "JOINED = joined(BOUNDED, SEP)\n",
    "parser = Parser(JOINED)\n",
    "match = parser.match('[a b], [c d], [e f g]')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Правило для BIO-разметки:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Симферополе']\n",
      "['Леонид', 'Рожков']\n"
     ]
    }
   ],
   "source": [
    "def bio(type):\n",
    "    return rule(\n",
    "        tag('B-%s' % type),\n",
    "        tag('I-%s' % type).repeatable().optional()\n",
    "    )\n",
    "\n",
    "\n",
    "PER = bio('PER')\n",
    "LOC = bio('LOC')\n",
    "\n",
    "\n",
    "text = '15 апреля в Симферополе Леонид Рожков ...'\n",
    "tags = 'B-DATE I-DATE O B-LOC B-PER I-PER O'.split()\n",
    "\n",
    "\n",
    "class Tagger(Tagger):\n",
    "    tags = {'B-PER', 'I-PER', 'B-LOC', 'I-LOC', 'O'}\n",
    "\n",
    "    def __call__(self, tokens):\n",
    "        for token, tag in zip(tokens, tags):\n",
    "            yield token.tagged(tag)\n",
    "\n",
    "\n",
    "RULE = or_(PER, LOC)\n",
    "parser = Parser(RULE, tagger=Tagger())\n",
    "matches = parser.findall(text)\n",
    "for match in matches:\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Генерация `pipeline`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим `pipeline` из словаря пар \"полное название\", \"сокращение\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Акционерное', 'общество']\n",
      "['Акционерном', 'обществе']\n",
      "['АО']\n",
      "['СК']\n"
     ]
    }
   ],
   "source": [
    "from yargy.pipelines import (\n",
    "    morph_pipeline,\n",
    "    pipeline\n",
    ")\n",
    "from yargy import interpretation as interp\n",
    "\n",
    "\n",
    "TYPES = [\n",
    "    ('Общество с ограниченной ответственностью', 'ООО'),\n",
    "    ('Акционерное общество', 'АО'),\n",
    "    ('Страховая компания', 'СК'),\n",
    "    ('Строительная компания', 'СК')\n",
    "]\n",
    "\n",
    "TYPE = or_(\n",
    "    morph_pipeline([\n",
    "        name for name, abbr in TYPES\n",
    "    ]),\n",
    "    pipeline([\n",
    "        abbr for name, abbr in TYPES\n",
    "    ])\n",
    ")\n",
    "\n",
    "RULE = TYPE.repeatable()\n",
    "parser = Parser(RULE)\n",
    "matches = parser.findall('Акционерное общество, в Акционерном обществе; АО, СК')\n",
    "for match in matches:\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Наследование `fact`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`fact` создаёт Python-класс, отнаследуемся, добавим методы и атрибуты. Например, есть ссылка на статьи \"ст. 15-17 п.1\", результат список объектов `Ref(art=15, punkt=1), Ref(art=16, punkt=1), ...`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ст. 15-17 п.1\n",
      "[Ref(art=15, punkt=1), Ref(art=16, punkt=1), Ref(art=17, punkt=1)]\n",
      "ст. 15 п.2\n",
      "[Ref(art=15, punkt=2)]\n",
      "ст. 16\n",
      "[Ref(art=16, punkt=None)]\n"
     ]
    }
   ],
   "source": [
    "from collections import namedtuple\n",
    "\n",
    "from yargy.predicates import type\n",
    "\n",
    "\n",
    "Ref_ = namedtuple(\n",
    "    'Ref',\n",
    "    ['art', 'punkt']\n",
    ")\n",
    "\n",
    "\n",
    "Art = fact(\n",
    "    'Art',\n",
    "    ['start', 'stop']\n",
    ")\n",
    "class Art(Art):\n",
    "    def range(self):\n",
    "        if self.stop:\n",
    "            return range(self.start, self.stop + 1)\n",
    "        else:\n",
    "            return [self.start]\n",
    "\n",
    "\n",
    "Punkt = fact(\n",
    "    'Punkt',\n",
    "    ['number']\n",
    ")\n",
    "\n",
    "\n",
    "Ref = fact(\n",
    "    'Ref',\n",
    "    ['art', 'punkt']\n",
    ")\n",
    "class Ref(Ref):\n",
    "    def range(self):\n",
    "        for art in self.art.range():\n",
    "            punkt = (\n",
    "                self.punkt.number\n",
    "                if self.punkt\n",
    "                else None\n",
    "            )\n",
    "            yield Ref_(art, punkt)\n",
    "            \n",
    "            \n",
    "INT = type('INT')\n",
    "\n",
    "ART = rule(\n",
    "    'ст', '.',\n",
    "    INT.interpretation(Art.start.custom(int)),\n",
    "    rule(\n",
    "        '-',\n",
    "        INT.interpretation(Art.stop.custom(int))\n",
    "    ).optional()\n",
    ").interpretation(Art)\n",
    "\n",
    "PUNKT = rule(\n",
    "    'п', '.',\n",
    "    INT.interpretation(Punkt.number.custom(int))\n",
    ").interpretation(Punkt)\n",
    "\n",
    "REF = rule(\n",
    "    ART.interpretation(Ref.art),\n",
    "    PUNKT.optional().interpretation(Ref.punkt)\n",
    ").interpretation(Ref)\n",
    "\n",
    "parser = Parser(REF)\n",
    "lines = [\n",
    "    'ст. 15-17 п.1',\n",
    "    'ст. 15 п.2',\n",
    "    'ст. 16'\n",
    "]\n",
    "for line in lines:\n",
    "    print(line)\n",
    "    match = parser.match(line)\n",
    "    print(list(match.fact.range()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть периоды \"1917-1918г.\", \"21 век\", приведём их к единому формату: `Period(1917, 1919)`, `Period(2000, 2100)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1917-1918г.\n",
      "Period(start=1917, stop=1919)\n",
      "21 век\n",
      "Period(start=2000, stop=2100)\n",
      "1990г.\n",
      "Period(start=1990, stop=1991)\n"
     ]
    }
   ],
   "source": [
    "Period_ = namedtuple('Period', ['start', 'stop'])\n",
    "\n",
    "\n",
    "Year = fact(\n",
    "    'Year',\n",
    "    ['value']\n",
    ")\n",
    "class Year(Year):\n",
    "    @property\n",
    "    def normalized(self):\n",
    "        return Period_(self.value, self.value + 1)\n",
    "\n",
    "\n",
    "YearRange = fact(\n",
    "    'YearRange',\n",
    "    ['start', 'stop']\n",
    ")\n",
    "class YearRange(YearRange):\n",
    "    @property\n",
    "    def normalized(self):\n",
    "        return Period_(self.start, self.stop + 1)\n",
    "    \n",
    "    \n",
    "Century = fact(\n",
    "    'Century',\n",
    "    ['value']\n",
    ")\n",
    "class Century(Century):\n",
    "    @property\n",
    "    def normalized(self):\n",
    "        start = (self.value - 1) * 100\n",
    "        return Period_(start, start + 100)\n",
    "\n",
    "\n",
    "Period = fact(\n",
    "    'Period',\n",
    "    ['value']\n",
    ")\n",
    "class Period(Period):\n",
    "    @property\n",
    "    def normalized(self):\n",
    "        return self.value.normalized\n",
    "\n",
    "\n",
    "YEAR = rule(\n",
    "    INT.interpretation(Year.value.custom(int)),\n",
    "    'г', '.'\n",
    ").interpretation(Year)\n",
    "\n",
    "YEAR_RANGE = rule(\n",
    "    INT.interpretation(YearRange.start.custom(int)),\n",
    "    '-',\n",
    "    INT.interpretation(YearRange.stop.custom(int)),\n",
    "    'г', '.'\n",
    ").interpretation(YearRange)\n",
    "\n",
    "CENTURY = rule(\n",
    "    INT.interpretation(Century.value.custom(int)),\n",
    "    'век'\n",
    ").interpretation(Century)\n",
    "\n",
    "PERIOD = or_(\n",
    "    YEAR,\n",
    "    YEAR_RANGE,\n",
    "    CENTURY\n",
    ").interpretation(Period.value).interpretation(Period)\n",
    "\n",
    "parser = Parser(PERIOD)\n",
    "lines = [\n",
    "    '1917-1918г.',\n",
    "    '21 век',\n",
    "    '1990г.'\n",
    "]\n",
    "for line in lines:\n",
    "    match = parser.match(line)\n",
    "    print(line)\n",
    "    print(match.fact.normalized)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
